package com.gitee.hermer.boot.jee.commons.utils;

import java.io.*;
import java.net.URI;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.gitee.hermer.boot.jee.commons.collection.CollectionUtils;
import com.gitee.hermer.boot.jee.commons.constants.EncodingConstant;
import com.gitee.hermer.boot.jee.commons.constants.OSType;
import com.gitee.hermer.boot.jee.commons.io.IOUtils;
import com.gitee.hermer.boot.jee.commons.log.Logger;
import com.gitee.hermer.boot.jee.commons.reflect.ClassUtils;

public class FileUtils {

	static Logger logger = Logger.getLogger(FileUtils.class);


	/**默认的标准推荐使用的文件路径分隔符*/
	public static final String UNIX_SEPARATOR = "/";

	/** 默认的不推荐使用的文件路径分隔符*/
	public static final String WINDOWS_SEPARATOR = "\\";

	/** Class文件扩展名 */
	public static final String CLASS_EXT = ".class";
	/** Jar文件扩展名 */
	public static final String JAR_FILE_EXT = ".jar";
	/** 在Jar中的路径jar的扩展名形式 */
	public static final String JAR_PATH_EXT = ".jar!";
	/** 当Path为文件形式时, path会加入一个表示文件的前缀 */
	public static final String PATH_FILE_PRE = "file:";

	/**默认的http协议地址头*/
	public static final String DEFAULT_PREFIX_HTTP_PROTOCOL = "http://";

	/**默认文件copy缓冲区大小*/
	public static final int FILE_COPY_BUFFER_SIZE = 20 * 1024 * 1024;


	private FileUtils() {
		// 工具类无需对象实例化
	}

	/**
	 * <p>
	 * 纠正不标准的文件路径分隔符 如：\,\\,\\\,//,/// -> /
	 * </p>
	 * 
	 * @param path
	 * @return
	 */
	public static String formatFilePath(String path) {
		if (!StringUtils.isEmpty(path)) {
			boolean startWithHttpProtocol = path.toLowerCase().startsWith(DEFAULT_PREFIX_HTTP_PROTOCOL);
			if (startWithHttpProtocol) {
				path = path.substring(DEFAULT_PREFIX_HTTP_PROTOCOL.length());
			}
			// 将一个或多个“\”转化成“/”
			path = path.replaceAll("\\\\{1,}", "/");
			// 将多个“/”转化成一个“/”
			path = path.replaceAll("\\/{2,}", "/");
			if (startWithHttpProtocol) {
				path = DEFAULT_PREFIX_HTTP_PROTOCOL + path;
			}
		}
		return path;
	}

	/**
	 * 获取当前工作目录
	 * @return 当前工作目录
	 */
	public static String getWorkPath() {
		return System.getProperty("user.dir");
	}

	/**
	 * 得到文件的输入流，如无法定位文件返回null。
	 * @param relativePath 文件相对当前应用程序的类加载器的路径。
	 * @return 文件的输入流。
	 */
	public static InputStream getResourceStream(String relativePath) {
		return Thread.currentThread().getContextClassLoader().getResourceAsStream(relativePath);
	}

	/**
	 * 关闭输入流。
	 * @param is 输入流，可以是null。
	 */
	public static void closeInputStream(InputStream is) {
		if (is != null) {
			try {
				is.close();
			} catch (IOException e) {
			}
		}
	}

	public static void closeFileOutputStream(FileOutputStream fos) {
		if (fos != null) {
			try {
				fos.close();
			} catch (IOException e) {
			}
		}
	}

	/**
	 * <p>
	 * 获取文件格式,小写,例如: txt、jpg等
	 * </p>
	 *
	 * @param imageFileName
	 * @return
	 */
	public static String getFileFormat(String fileName) {
		return fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
	}

	@SuppressWarnings("rawtypes")
	public static String getJarExecPath(Class clazz) {
		String path = clazz.getProtectionDomain().getCodeSource().getLocation().getPath();
		if (OSUtils.getOSname().equals(OSType.Windows)) {
			return path.substring(1);
		}
		return path;
	}

	/**
	 * <p>
	 * 获取文件大小,单位字节
	 * </p>
	 * @param fileFullPath
	 * @return
	 * @throws IOException
	 */
	public static int getFileSize(String fileFullPath) throws IOException {
		int size = 0;
		fileFullPath = formatFilePath(fileFullPath);
		File file = new File(fileFullPath);
		if (file.exists() && !isDirectory(fileFullPath)) {
			FileInputStream fis = new FileInputStream(file);
			size = fis.available();
			if (fis != null) {
				fis.close();
			}
		}
		return size;
	}

	/**
	 * <p>
	 * 根据文件路径获取其目录
	 * </p>
	 *
	 * @param filePath
	 * @return
	 */
	public static String getFileDirectory(String filePath) {
		if (!StringUtils.isEmpty(filePath)) {
			filePath = formatFilePath(filePath);
			if (isDirectory(filePath)) {
				return filePath;
			} else {
				return filePath.substring(0, filePath.lastIndexOf(UNIX_SEPARATOR));
			}
		}
		return filePath;
	}


	/**
	 * <p>
	 * 重命名文件名
	 * </p>
	 * @param originalName  - 原文件名
	 * @param renameAll - true-舍弃原文件名完全做随机重新命名;false-在原文件名后面做随机重命名
	 * @param appendStr - 加在文件名后的追加后缀,e.g. ${originalName}_${appendStr}.jpg
	 * @return
	 * @throws Exception
	 */
	public static String renameFileName(String originalName, boolean renameAll, String appendStr) {
		String suffix = originalName.substring(originalName.lastIndexOf('.') + 1);
		String fileName = originalName.substring(0, originalName.lastIndexOf('.'));
		String randomName = UUID.randomUUID().toString().replace("-", "");
		if (!StringUtils.isEmpty(appendStr)) {
			return String.format("%s_%s.%s", renameAll ? randomName : fileName + "_" + randomName.substring(0, 8), appendStr, suffix);
		} else {
			return String.format("%s.%s", renameAll ? randomName : fileName + "_" + randomName.substring(0, 8), suffix);
		}
	}



	/**
	 * 根据完整文件名，得到文件所在目录
	 *
	 * @param filePath
	 * @return
	 */
	public static String getDirName(String filePath) {
		File f = new File(filePath);
		return f.getParent();
	}

	/**
	 * 获得随机的文件名路径
	 *
	 * @param filePath
	 * @return
	 */
	public static String getAutoFileName(String filePath) {
		long curtime = System.nanoTime();
		String fileName = curtime + ".txt";
		File ff = new File(filePath + File.separator + fileName);
		if (ff.exists())
			return null;
		return ff.getAbsolutePath();
	}

	/**
	 * 当前目录路径
	 */
	public static String currentWorkDir = System.getProperty("user.dir") + "/";

	/**
	 * 加载classPath下的属性文件
	 *
	 * @param fileName
	 *            比如：“/properties/mail.properties”
	 * @return
	 * @throws Exception
	 */
	public static Properties loadProperties(String fileName) {
		try {
			Properties p = new Properties();
			p.load(FileUtils.class.getResourceAsStream(fileName));
			return p;
		} catch (Exception e) {
			// logger.error(fileName + " loadProperties error", e);
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 左填充
	 * 
	 * @param str
	 * @param length
	 * @param ch
	 * @return
	 */
	public static String leftPad(String str, int length, char ch) {
		if (str.length() >= length) {
			return str;
		}
		char[] chs = new char[length];
		Arrays.fill(chs, ch);
		char[] src = str.toCharArray();
		System.arraycopy(src, 0, chs, length - src.length, src.length);
		return new String(chs);

	}

	/**
	 * <p> 根据文件路径获取File对象 </p>
	 * @param fullFilePath
	 * @return
	 */
	public static File getFile(String filePath) {
		filePath = formatFilePath(filePath);
		return new File(filePath);
	}

	/**
	 * 删除文件
	 * @param fileName 待删除的完整文件名
	 * @return
	 */
	public static boolean delete(String fileName) {
		boolean result = false;
		File f = new File(fileName);
		if (f.exists()) {
			result = f.delete();

		} else {
			result = true;
		}
		return result;
	}

	/**
	 * <p> 文件复制 </p>
	 *
	 * @param srcFile  - 源文件
	 * @param destFile - 目标文件
	 * @throws Exception
	 */
	public static void copyFile(File srcFile, File destFile) throws Exception {
		FileInputStream fis = null;
		FileOutputStream fos = null;
		FileChannel input = null;
		FileChannel output = null;
		try {
			fis = new FileInputStream(srcFile);
			fos = new FileOutputStream(destFile);
			input = fis.getChannel();
			output = fos.getChannel();
			long size = input.size();
			long pos = 0;
			long count = 0;
			while (pos < size) {
				count = size - pos > FILE_COPY_BUFFER_SIZE ? FILE_COPY_BUFFER_SIZE : size - pos;
				pos += output.transferFrom(input, pos, count);
			}
		} finally {
			IOUtils.closeQuietly(output);
			IOUtils.closeQuietly(fos);
			IOUtils.closeQuietly(input);
			IOUtils.closeQuietly(fis);
		}
	}

	/**
	 * <p> 文件复制 </p>
	 * @param srcFullFileName - 源文件名
	 * @param destFullFileName - 目标文件名
	 * @throws Exception
	 */
	public static void copyFile(String srcFullFileName, String destFullFileName) throws Exception {
		copyFile(getFile(srcFullFileName), getFile(destFullFileName));
	}

	/**
	 * <p> 删除文件 </p>
	 * @param fullPath
	 * @return
	 */
	public static boolean deleteFile(String fullPath) {
		File file = getFile(fullPath);
		if (file.exists()) {
			return file.delete();
		}
		return false;
	}

	/**
	 * <p> 尽最大努力删除文件,删除失败不抛出异常 </p>
	 * @param fullPath
	 * @return
	 */
	public static void deleteFileQuietly(String fullPath) {
		try {
			File file = getFile(fullPath);
			if (file.exists()) {
				file.delete();
			}
		} catch (Exception e) {
		}
	}

	/**
	 * 递归获取指定目录下的所有的文件（不包括文件夹）
	 * @return
	 */
	public static ArrayList<File> getAllFiles(String dirPath) {
		File dir = new File(dirPath);
		ArrayList<File> files = new ArrayList<File>();

		if (dir.isDirectory()) {
			File[] fileArr = dir.listFiles();
			for (int i = 0; i < fileArr.length; i++) {
				File f = fileArr[i];
				if (f.isFile()) {
					files.add(f);
				} else {
					files.addAll(getAllFiles(f.getPath()));
				}
			}
		}
		return files;
	}

	/**
	 * 获取指定目录下的所有下一级子目录(不递归)
	 * @param dirPath
	 * @return
	 */
	public static ArrayList<File> getAllDirectory(String dirPath) {
		return getAllDirectory(dirPath, false);
	}

	/**
	 * 获取指定路径下的所有目录
	 * @param dirPath
	 * @param isRecursive
	 * @return
	 */
	public static ArrayList<File> getAllDirectory(String dirPath, boolean isRecursive) {
		File dir = new File(dirPath);
		ArrayList<File> files = new ArrayList<File>();
		if (dir.isDirectory()) {
			File[] fileArr = dir.listFiles();
			for (int i = 0; i < fileArr.length; i++) {
				File f = fileArr[i];
				if (f.isDirectory()) {
					files.add(f);
					if (isRecursive) {
						files.addAll(getAllDirectory(f.getPath(), true));
					}
				}
			}
		}
		return files;
	}

	/**
	 * 获取指定目录下的所有文件(不包括子文件夹)
	 * @param dirPath
	 * @return
	 */
	public static ArrayList<File> getDirFiles(String dirPath) {
		File path = new File(dirPath);
		File[] fileArr = path.listFiles();
		ArrayList<File> files = new ArrayList<File>();

		if (fileArr == null) {
			return files;
		}

		for (File f : fileArr) {
			if (f.isFile()) {
				files.add(f);
			}
		}
		return files;
	}

	/**
	 * 获取指定目录下特定文件后缀名的文件列表(不包括子文件夹)
	 * @param dirPath  目录路径
	 * @param suffix 文件后缀
	 * @return
	 */
	public static List<File> getDirFiles(String dirPath, final String suffix) {
		File path = new File(dirPath);
		File[] fileArr = path.listFiles(new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				String lowerName = name.toLowerCase();
				String lowerSuffix = suffix.toLowerCase();
				if (lowerName.endsWith(lowerSuffix)) {
					return true;
				}
				return false;
			}

		});
		List<File> files = new ArrayList<File>();

		for (File f : fileArr) {
			if (f.isFile()) {
				files.add(f);
			}
		}
		return files;
	}

	/**
	 * 批量删除文件
	 * @param files
	 */
	public static void delete(List<File> files) {
		for (int i = files.size() - 1; i >= 0; i--) {
			File f = files.get(i);
			System.out.println("准备删除文件：" + f.getAbsolutePath());
			if (f.exists()) {
				if (f.delete()) {
					System.out.println("文件：" + f.getAbsolutePath() + " 删除成功！");
				} else {
					System.out.println("文件：" + f.getAbsolutePath() + " 删除失败！");
				}
			}
		}

	}

	/**
	 * 通过前缀得到指定文件夹下的文件列表
	 * @param dirPath
	 * @param prefix
	 * @return
	 */
	public static List<File> getDirFilesByPrefix(String dirPath, final String prefix) {
		File path = new File(dirPath);
		File[] fileArr = path.listFiles(new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				String lowerName = name.toLowerCase();
				String lowerPrefix = prefix.toLowerCase();
				if (lowerName.startsWith(lowerPrefix)) {
					return true;
				}
				return false;
			}

		});
		List<File> files = new ArrayList<File>();

		for (File f : fileArr) {
			if (f.isFile()) {
				files.add(f);
			}
		}
		return files;
	}

	public static List<File> getDirFilesByPrefixAndSurfix(String dirPath, final String prefix, final String surfix) {
		File path = new File(dirPath);
		File[] fileArr = path.listFiles(new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				String lowerName = name.toLowerCase();
				String lowerPrefix = prefix.toLowerCase();
				if (lowerName.startsWith(lowerPrefix) && lowerName.endsWith(surfix)) {
					return true;
				}
				return false;
			}

		});
		List<File> files = new ArrayList<File>();

		for (File f : fileArr) {
			if (f.isFile()) {
				files.add(f);
			}
		}
		return files;
	}

	public static List<File> getDirFilesByPrefix(String dirPath, final String prefix, final String execludePrefix) {
		File path = new File(dirPath);
		File[] fileArr = path.listFiles(new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				String lowerName = name.toLowerCase();
				String lowerPrefix = prefix.toLowerCase();
				String lowerExecludePrefix = execludePrefix.toLowerCase();
				if (lowerName.startsWith(lowerPrefix) && !lowerName.startsWith(lowerExecludePrefix)) {
					return true;
				}
				return false;
			}

		});
		List<File> files = new ArrayList<File>();

		for (File f : fileArr) {
			if (f.isFile()) {
				files.add(f);
			}
		}
		return files;
	}

	/**
	 * 读取文件内容
	 * @param fileName 待读取的完整文件名
	 * @return 文件内容
	 * @throws IOException
	 */
	public static String read(String fileName) throws IOException {
		File f = new File(fileName);
		if (!f.exists()) {
			return null;
		}
		FileInputStream fs = new FileInputStream(f);
		String result = null;
		byte[] b = new byte[fs.available()];
		fs.read(b);
		fs.close();
		result = new String(b);
		return result;
	}

	/**
	 * 读取文件内容
	 * @param file
	 * @return
	 * @throws IOException
	 */
	public static String read(File file) throws IOException {
		FileInputStream fs = new FileInputStream(file);
		String result = null;
		byte[] b = new byte[fs.available()];
		fs.read(b);
		fs.close();
		result = new String(b);
		return result;
	}

	/**
	 * 读取文件内容到string
	 * @param file
	 * @return
	 * @throws IOException
	 */
	public static String read(File file, String charsetName) throws IOException {
		if (file == null)
			return null;
		BufferedReader bf = new BufferedReader(new InputStreamReader(new FileInputStream(file), charsetName));
		String content;
		StringBuilder sb = new StringBuilder();
		while (true) {
			content = bf.readLine();
			if (content == null) {
				break;
			}
			sb.append(content.trim());
		}
		bf.close();
		return sb.toString();
	}

	/**
	 * 写文件
	 * @param fileName 目标文件名
	 * @param fileContent 写入的内容
	 * @return
	 * @throws IOException
	 */
	public static void write(String fileName, String fileContent) throws IOException {
		write(fileName, fileContent, true, true);
	}

	/**
	 * 写文件
	 * @param fileName 完整文件名(类似：/usr/a/b/c/d.txt)
	 * @param fileContent 文件内容
	 * @param autoCreateDir 目录不存在时，是否自动创建(多级)目录
	 * @param autoOverwrite 目标文件存在时，是否自动覆盖
	 * @return
	 * @throws IOException
	 */
	public static void write(String fileName, String fileContent, boolean autoCreateDir, boolean autoOverwrite) throws IOException {
		write(fileName, fileContent.getBytes(), autoCreateDir, autoOverwrite);
	}

	/**
	 * 写文件
	 * @param fileName 完整文件名(类似：/usr/a/b/c/d.txt)
	 * @param contentBytes 文件内容的字节数组
	 * @param autoCreateDir 目录不存在时，是否自动创建(多级)目录
	 * @param autoOverwrite 目标文件存在时，是否自动覆盖
	 * @return
	 * @throws IOException
	 */
	public static void write(String fileName, byte[] contentBytes, boolean autoCreateDir, boolean autoOverwrite) throws IOException {

		if (autoCreateDir) {
			createDirs(fileName);
		}
		if (autoOverwrite) {
			delete(fileName);
		}
		File f = new File(fileName);
		FileOutputStream fs = new FileOutputStream(f);
		fs.write(contentBytes);
		fs.flush();
		fs.close();
	}

	/**
	 * 追加内容到指定文件
	 * @param fileName
	 * @param fileContent
	 * @return
	 * @throws IOException
	 */
	public static void append(String fileName, String fileContent, boolean createFileIfNoExist) throws IOException {
		File f = new File(fileName);
		if (f.exists()) {
			RandomAccessFile rFile = new RandomAccessFile(f, "rw");
			byte[] b = fileContent.getBytes();
			long originLen = f.length();
			rFile.setLength(originLen + b.length);
			rFile.seek(originLen);
			rFile.write(b);
			rFile.close();
		} else if (createFileIfNoExist) {
			write(fileName, fileContent);
		}
	}

	public static void append(String fileName, String fileContent) throws IOException {
		append(fileName, fileContent, true);
	}

	private static class FileComparator implements Comparator<File> {
		@Override
		public int compare(File o1, File o2) {
			return o1.getName().compareToIgnoreCase(o2.getName());
		}
	}

	/**
	 * 创建(多级)目录
	 * @param filePath 完整的文件名(类似：/usr/a/b/c/d.xml)
	 */
	public static void createDirs(String filePath) {

		File file = new File(filePath);
		if (file.exists()) {
			return;
		}
		File parent = file.getParentFile();
		if (parent != null && !parent.exists()) {
			parent.mkdirs();
		}

	}

	/**
	 * 移动文件
	 * @param fileNameFrom
	 * @param fileNameTo
	 * @throws IOException
	 */
	public static void moveFile(String fileNameFrom, String fileNameTo) throws IOException {
		write(fileNameTo, read(fileNameFrom));
		delete(fileNameFrom);

	}

	/**
	 * 拆分文件
	 * @param fileName 待拆分的完整文件名
	 * @param byteSize 按多少字节大小拆分
	 * @return 拆分后的文件名列表
	 * @throws IOException
	 */
	public List<String> splitBySize(String fileName, int byteSize) throws IOException {
		List<String> parts = new ArrayList<String>();
		File file = new File(fileName);
		int count = (int) Math.ceil(file.length() / (double) byteSize);
		int countLen = (count + "").length();
		ThreadPoolExecutor threadPool = new ThreadPoolExecutor(count, count * 3, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(count * 2));

		for (int i = 0; i < count; i++) {
			String partFileName = file.getName() + "." + leftPad((i + 1) + "", countLen, '0') + ".part";
			threadPool.execute(new SplitRunnable(byteSize, i * byteSize, partFileName, file));
			parts.add(partFileName);
		}
		return parts;
	}

	/**
	 * 合并文件
	 * @param dirPath  拆分文件所在目录名
	 * @param partFileSuffix 拆分文件后缀名
	 * @param partFileSize 拆分文件的字节数大小
	 * @param mergeFileName 合并后的文件名
	 * @throws IOException
	 */
	public static void mergePartFiles(String dirPath, String partFileSuffix, int partFileSize, String mergeFileName) throws IOException {
		List<File> partFiles = FileUtils.getDirFiles(dirPath, partFileSuffix);
		Collections.sort(partFiles, new FileComparator());

		RandomAccessFile randomAccessFile = new RandomAccessFile(mergeFileName, "rw");
		randomAccessFile.setLength(partFileSize * (partFiles.size() - 1) + partFiles.get(partFiles.size() - 1).length());
		randomAccessFile.close();

		ThreadPoolExecutor threadPool = new ThreadPoolExecutor(partFiles.size(), partFiles.size() * 3, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(partFiles.size() * 2));

		for (int i = 0; i < partFiles.size(); i++) {
			threadPool.execute(new MergeRunnable(i * partFileSize, mergeFileName, partFiles.get(i)));
		}

	}

	/**
	 * @ClassName: SplitRunnable 
	 * @Description: 分割处理Runnable 
	 * @author wuzhenfang(wzfbj2008@163.com)
	 * @date 2016年4月30日 上午7:56:46 
	 * @version V1.0
	 */
	private static class SplitRunnable implements Runnable {
		int byteSize;
		String partFileName;
		File originFile;
		int startPos;

		public SplitRunnable(int byteSize, int startPos, String partFileName, File originFile) {
			this.startPos = startPos;
			this.byteSize = byteSize;
			this.partFileName = partFileName;
			this.originFile = originFile;
		}

		@Override
		public void run() {
			RandomAccessFile rFile;
			OutputStream os;
			try {
				rFile = new RandomAccessFile(originFile, "r");
				byte[] b = new byte[byteSize];
				rFile.seek(startPos);// 移动指针到每“段”开头
				int s = rFile.read(b);
				os = new FileOutputStream(partFileName);
				os.write(b, 0, s);
				os.flush();
				os.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * @ClassName: MergeRunnable 
	 * @Description: 合并处理Runnable 
	 * @author wuzhenfang(wzfbj2008@163.com)
	 * @date 2016年4月30日 上午7:57:00 
	 * @version V1.0
	 */
	private static class MergeRunnable implements Runnable {
		long startPos;
		String mergeFileName;
		File partFile;

		public MergeRunnable(long startPos, String mergeFileName, File partFile) {
			this.startPos = startPos;
			this.mergeFileName = mergeFileName;
			this.partFile = partFile;
		}

		@Override
		public void run() {
			RandomAccessFile rFile;
			try {
				rFile = new RandomAccessFile(mergeFileName, "rw");
				rFile.seek(startPos);
				FileInputStream fs = new FileInputStream(partFile);
				byte[] b = new byte[fs.available()];
				fs.read(b);
				fs.close();
				rFile.write(b);
				rFile.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 获取绝对路径<br/>
	 * 此方法不会判定给定路径是否有效（文件或目录存在）
	 * @param path 相对路径
	 * @param baseClass 相对路径所相对的类
	 * @return 绝对路径
	 */
	public static String getAbsolutePath(String path, Class<?> baseClass) {
		if (path == null) {
			path = StringUtils.EMPTY;
		}
		if (baseClass == null) {
			return getAbsolutePath(path);
		}
		// return baseClass.getResource(path).getPath();
		return StringUtils.removePrefix(PATH_FILE_PRE, baseClass.getResource(path).getPath());
	}

	/**
	 * 获取绝对路径，相对于classes的根目录<br>
	 * 如果给定就是绝对路径，则返回原路径，原路径把所有\替换为/
	 * 
	 * @param path 相对路径
	 * @return 绝对路径
	 */
	public static String getAbsolutePath(String path) {
		if (path == null) {
			path = StringUtils.EMPTY;
		} else {
			path = normalize(path);

			if (path.startsWith("/") || path.matches("^[a-zA-Z]:/.*")) {
				// 给定的路径已经是绝对路径了
				return path;
			}
		}

		// 相对路径
		ClassLoader classLoader = ClassUtils.getClassLoader();
		URL url = classLoader.getResource(path);
		String reultPath = url != null ? url.getPath() : ClassUtils.getClassPath() + path;
		// return StringUtils.removePrefix(reultPath, PATH_FILE_PRE);
		return reultPath;
	}

	/**
	 * 获取标准的绝对路径
	 * 
	 * @param file 文件
	 * @return 绝对路径
	 */
	public static String getAbsolutePath(File file) {
		if (file == null) {
			return null;
		}

		try {
			return file.getCanonicalPath();
		} catch (IOException e) {
			return file.getAbsolutePath();
		}
	}

	/**
	 * 获得一个输出流对象
	 * @param path 输出到的文件路径，绝对路径
	 * @return 输出流对象
	 * @throws IOException
	 */
	public static OutputStream getOutputStream(String path) throws IOException {
		return new FileOutputStream(touch(path));
	}


	/**
	 * 清空一个目录
	 * @param dirPath 需要删除的文件夹路径
	 */
	public static void cleanDir(String dirPath){
		File dir = new File(dirPath);
		if(dir.exists() && dir.isDirectory()){
			File[] files = dir.listFiles();
			for (File file : files) {
				if(file.isDirectory()) cleanDir(file.getAbsolutePath());
				file.delete();
			}
		}
	}

	/**
	 * 关闭
	 * @param closeable 被关闭的对象
	 */
	public static void close(Closeable closeable){
		if(closeable == null) return;
		try {
			closeable.close();
		} catch (IOException e) {
		}
	}

	/**
	 * 获得文件的扩展名
	 * @param fileName 文件名
	 * @return 扩展名
	 */
	public static String getExtension(String fileName) {
		if (fileName == null) {
			return null;
		}
		int index = fileName.lastIndexOf(".");
		if (index == -1) {
			return "";
		} else {
			String ext = fileName.substring(index + 1);
			//扩展名中不能包含路径相关的符号
			return (ext.contains("/") || ext.contains("\\")) ? "" : ext;
		}
	}

	/**
	 * 从文件中读取每一行数据
	 * @param path	文件路径
	 * @param charset	字符集
	 * @param collection	集合
	 * @return	文件中的每行内容的集合
	 * @throws IOException
	 */
	public static <T extends Collection<String>> T loadFileLines(String path, String charset, T collection) throws IOException{
		BufferedReader reader = getReader(path, charset);
		while(true){
			String line = reader.readLine();
			if(line == null) break;
			collection.add(line);
		}
		close(reader);
		return collection;
	}

	/**
	 * 按照给定的readerHandler读取文件中的数据
	 * @param readerHandler Reader处理类
	 * @param path 文件的绝对路径
	 * @param charset 字符集
	 * @return 从文件中load出的数据
	 * @throws IOException
	 */
	public static <T> T loadDataFromfile(ReaderHandler<T> readerHandler, String path, String charset) throws IOException {
		BufferedReader reader = null;
		T result = null;
		try {
			reader = getReader(path, charset);
			result = readerHandler.handle(reader);
		} catch (IOException e) {
			throw new IOException(e);
		}finally {
			FileUtils.close(reader);
		}
		return result;
	}

	/**
	 * Reader处理接口
	 * @param <T>
	 */
	public interface ReaderHandler<T> {
		public T handle(BufferedReader reader) throws IOException;
	}

	/**
	 * 获得一个文件读取器
	 * @param path 绝对路径
	 * @param charset 字符集
	 * @return BufferedReader对象
	 * @throws IOException
	 */
	public static BufferedReader getReader(String path, String charset) throws IOException{
		return new BufferedReader(new InputStreamReader(new FileInputStream(path), charset));
	}

	/**
	 * 将文件内容按行拆分成一个List<String>
	 * @param fileName 文件完整路径
	 * @return List<String> 所有所有行内容的一个集合
	 * @throws Exception
	 */
	public static List< String > fileToListByLine( String fileName ) throws Exception {

		List< String > lineList = new LinkedList< String >();
		String line = "";
		BufferedReader in = null;
		try {
			in = new BufferedReader( new FileReader( fileName ) );
			while ( ( line = in.readLine() ) != null ) {
				lineList.add( line );
			}
		} catch ( Exception e ) {
			throw e;
		} finally {
			if ( null != in ) {
				in.close();
			}
		}
		return lineList;
	}

	/**
	 * 将文件内容按行拆分成一个List<String>
	 * @param fileContent 文件内容
	 * @return List<String> 所有所有行内容的一个集合
	 * @throws Exception
	 */
	public static List< String > stringToListByLine( final String fileContent ) throws Exception {

		List< String > lineList = new LinkedList< String >();

		for ( String line : fileContent.split( "\n" ) ) {
			lineList.add( line );
		}

		return lineList;
	}

	// list sorted files
	public static File[] listSortedFiles( File dirFile ) {

		assert dirFile.isDirectory();

		File[] files = dirFile.listFiles();

		FileWrapper[] fileWrappers = new FileWrapper[files.length];
		for ( int i = 0; i < files.length; i++ ) {
			fileWrappers[i] = new FileWrapper( files[i] );
		}

		Arrays.sort( fileWrappers );

		File[] sortedFiles = new File[files.length];
		for ( int i = 0; i < files.length; i++ ) {
			sortedFiles[i] = fileWrappers[i].getFile();
		}

		return sortedFiles;
	}

	// list sorted files, with special fileName filter
	public static File[] listSortedFiles( File dirFile, final String fileNameFilter ) {

		assert dirFile.isDirectory();

		File[] files = dirFile.listFiles( new FilenameFilter() {

			@Override
			public boolean accept( File dir, String name ) {

				if ( StringUtils.isBlank( name ) ) {
					return false;
				}

				// Must statistics history file
				if ( name.startsWith( fileNameFilter ) && !name.endsWith( "log" ) ) {
					return true;
				}
				return false;
			}
		} );

		FileWrapper[] fileWrappers = new FileWrapper[files.length];
		for ( int i = 0; i < files.length; i++ ) {
			fileWrappers[i] = new FileWrapper( files[i] );
		}

		Arrays.sort( fileWrappers );

		File[] sortedFiles = new File[files.length];
		for ( int i = 0; i < files.length; i++ ) {
			sortedFiles[i] = fileWrappers[i].getFile();
		}

		return sortedFiles;
	}

	/**
	 * @param content
	 *            content need to write
	 * @param append
	 *            if <code>true</code>, then bytes will be written to the end of
	 *            the file rather than the beginning
	 * @throws IOException
	 */
	public static boolean write( String filePath, String content, boolean append ) throws IOException {
		FileWriter filewriter = null;
		try {
			File file = new File( filePath );
			filewriter = new FileWriter( file, append );
			filewriter.write( content );
			return true;
		} finally {
			IOUtils.closeWriter( filewriter );
		}
	}

	/**
	 * @param filePath
	 * @param content
	 * @param encode
	 * @return
	 */
	public static boolean write( String filePath, String content, String encode ) {
		try {
			OutputStreamWriter out = new OutputStreamWriter( new FileOutputStream( filePath ), encode );
			out.write( content );
			out.flush();
			out.close();
			return true;
		} catch ( Exception e ) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 使用GBK编码读取文件
	 * 
	 * @param filePath
	 *            文件路径
	 * @return String 文件内容
	 * @throws IOException
	 */
	public static String readFile( String filePath ) throws IOException {
		return readFile( filePath, EncodingConstant.GBK );
	}

	/**
	 * 使用指定编码读取文件,此方法无须用户关闭资源，方法内部已经全部关闭了。
	 * @param filePath 文件路径
	 * @param encoding 读取的编码
	 * @return String 文件内容
	 * @throws IOException
	 */
	public static String readFile( String filePath, String encoding ) throws IOException {

		File file = new File( filePath );
		FileInputStream fileInputStream = null;
		StringBuilder content = new StringBuilder();
		try {
			fileInputStream = new FileInputStream( file );
		} catch ( FileNotFoundException e ) {
			throw e;
		}

		BufferedReader br = new BufferedReader( new InputStreamReader( fileInputStream, encoding ) );
		String data = null;
		try {
			while ( ( data = br.readLine() ) != null ) {
				content.append( data ).append( "\n" );
			}
			return content.toString();
		} catch ( IOException e ) {
			throw new IOException( "读取文件异常: " + e.getMessage() );
		} finally {
			try {
				fileInputStream.close();
				br.close();
			} catch ( IOException e ) {
			}
		}
	}

	/**
	 * Note: You need to close Reader.
	 * @param filePath 文件路径
	 * @return String 文件内容
	 * @throws IOException
	 */
	public static Reader readFileReader( String filePath ) throws IOException {

		File file = new File( filePath );
		FileInputStream fileInputStream = null;
		try {
			fileInputStream = new FileInputStream( file );
		} catch ( FileNotFoundException e ) {
			throw e;
		}
		return new InputStreamReader( fileInputStream );
	}

	/**
	 * 递归遍历目录以及子目录中的所有文件
	 * @param file 当前遍历文件
	 * @param fileFilter 文件过滤规则对象，选择要保留的文件
	 */
	public static List<File> loopFiles(File file, FileFilter fileFilter) {
		List<File> fileList = new ArrayList<File>();
		if (file == null) {
			return fileList;
		} else if (file.exists() == false) {
			return fileList;
		}

		if (file.isDirectory()) {
			for (File tmp : file.listFiles()) {
				fileList.addAll(loopFiles(tmp, fileFilter));
			}
		} else {
			if (null == fileFilter || fileFilter.accept(file)) {
				fileList.add(file);
			}
		}

		return fileList;
	}


	/**
	 * 使用指定编码读取文件,此方法需要用户关闭资源
	 * @param filePath 文件路径
	 * @param encoding 读取的编码
	 * @return String 文件内容
	 * @throws IOException
	 */
	public static BufferedReader readFileReturnBufferedReader( String filePath, String encoding ) throws IOException {

		File file = new File( filePath );
		FileInputStream fileInputStream = null;
		try {
			fileInputStream = new FileInputStream( file );
		} catch ( FileNotFoundException e ) {
			throw e;
		}
		return new BufferedReader( new InputStreamReader( fileInputStream, encoding ) );
	}

	/**
	 * Read properties file.
	 * @param filePath
	 * @return
	 * @throws IOException
	 */
	public static Properties readPropertyFile( String filePath ) throws IOException {

		Properties properties = new Properties();
		Reader reader = null;
		try {
			reader = FileUtils.readFileReader( filePath );
			properties.load( reader );
			return properties;
		} finally {
			IOUtils.closeReader( reader );
		}
	}

	/**
	 * 列出目录文件<br>
	 * 给定的绝对路径不能是压缩包中的路径
	 * @param path 目录绝对路径或者相对路径
	 * @return 文件列表（包含目录）
	 * @throws Exception 
	 */
	public static File[] ls(String path) throws Exception {
		if (path == null) {
			return null;
		}
		path = getAbsolutePath(path);

		File file = file(path);
		if (file.isDirectory()) {
			return file.listFiles();
		}
		throw new Exception(StringUtils.format("Path [{}] is not directory!", path));
	}

	/**
	 * 文件是否为空<br>
	 * 目录：里面没有文件时为空
	 * 文件：文件大小为0时为空
	 * 
	 * @param file 文件
	 * @return 是否为空，当提供非目录时，返回false
	 */
	public static boolean isEmpty(File file) {
		if (null == file) {
			return true;
		}

		if (file.isDirectory()) {
			String[] subFiles = file.list();
			if (CollectionUtils.isEmpty(subFiles)) {
				return true;
			}
		}else if(file.isFile()){
			return file.length() <= 0;
		}

		return false;
	}

	/**
	 * 目录是否为空
	 * @param file 目录
	 * @return 是否为空，当提供非目录时，返回false
	 */
	public static boolean isNotEmpty(File file) {
		return false == isEmpty(file);
	}

	/**
	 * 目录是否为空
	 * @param dirPath 目录
	 * @return 是否为空
	 * @exception Exception IOException
	 */
	public static boolean isDirEmpty(Path dirPath) throws Exception {
		try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dirPath)) {
			return false == dirStream.iterator().hasNext();
		} catch (IOException e) {
			throw new Exception(e);
		}
	}

	/**
	 * 目录是否为空
	 * @param dir 目录
	 * @return 是否为空
	 * @throws Exception 
	 */
	public static boolean isDirEmpty(File dir) throws Exception {
		return isDirEmpty(dir.toPath());
	}


	/**
	 * 递归遍历目录以及子目录中的所有文件
	 * @param file 当前遍历文件
	 */
	public static List<File> loopFiles(File file) {
		List<File> fileList = new ArrayList<File>();
		if (file == null) {
			return fileList;
		} else if (file.exists() == false) {
			return fileList;
		}

		if (file.isDirectory()) {
			for (File tmp : file.listFiles()) {
				fileList.addAll(loopFiles(tmp));
			}
		} else {
			fileList.add(file);
		}
		return fileList;
	}



	/**
	 * 获得指定目录下所有文件<br>
	 * 不会扫描子目录
	 * @param path 相对ClassPath的目录或者绝对路径目录
	 * @return 文件路径列表（如果是jar中的文件，则给定类似.jar!/xxx/xxx的路径）
	 * @throws Exception 
	 * @throws IOException
	 */
	public static List<String> listFileNames(String path) throws Exception {
		if (path == null) {
			return null;
		}
		path = getAbsolutePath(path);
		if (path.endsWith(String.valueOf(UNIX_SEPARATOR)) == false) {
			path = path + UNIX_SEPARATOR;
		}

		List<String> paths = new ArrayList<String>();
		int index = path.lastIndexOf(FileUtils.JAR_PATH_EXT);
		try {
			if (index == -1) {
				// 普通目录路径
				File[] files = ls(path);
				for (File file : files) {
					if (file.isFile()) {
						paths.add(file.getName());
					}
				}
			} else {
				// jar文件中的路径
				index = index + FileUtils.JAR_FILE_EXT.length();
				final String jarPath = path.substring(0, index);
				final String subPath = path.substring(index + 2);
				for (JarEntry entry : Collections.list(new JarFile(jarPath).entries())) {
					final String name = entry.getName();
					if (name.startsWith(subPath)) {
						String nameSuffix = StringUtils.removePrefix(name, subPath);
						if (nameSuffix.contains(String.valueOf(UNIX_SEPARATOR)) == false) {
							paths.add(nameSuffix);
						}
					}
				}
			}
		} catch (Exception e) {
			throw new Exception(StringUtils.format("Can not read file path of [{}]", path), e);
		}
		return paths;
	}

	/**
	 * 创建File对象，自动识别相对或绝对路径，相对路径将自动从ClassPath下寻找
	 * @param path 文件路径
	 * @return File
	 */
	public static File file(String path) {
		if (StringUtils.isBlank(path)) {
			throw new NullPointerException("File path is blank!");
		}
		return new File(getAbsolutePath(path));
	}

	/**
	 * 创建File对象
	 * @param parent 父目录
	 * @param path 文件路径
	 * @return File
	 */
	public static File file(String parent, String path) {
		if (StringUtils.isBlank(path)) {
			throw new NullPointerException("File path is blank!");
		}
		return new File(parent, path);
	}

	/**
	 * 创建File对象
	 * @param parent 父文件对象
	 * @param path 文件路径
	 * @return File
	 */
	public static File file(File parent, String path) {
		if (StringUtils.isBlank(path)) {
			throw new NullPointerException("File path is blank!");
		}
		return new File(parent, path);
	}

	/**
	 * 创建File对象
	 * @param uri 文件URI
	 * @return File
	 */
	public static File file(URI uri) {
		if (uri == null) {
			throw new NullPointerException("File uri is null!");
		}
		return new File(uri);
	}

	/**
	 * 判断文件是否存在，如果path为null，则返回false
	 * @param path 文件路径
	 * @return 如果存在返回true
	 */
	public static boolean exist(String path) {
		return (path == null) ? false : file(path).exists();
	}

	/**
	 * 文件是否存在
	 * @param path 文件路径
	 * @return 是否存在
	 */
	public static boolean isExist(String path){
		return  new File(path).exists();
	}

	/**
	 * 判断文件是否存在，如果file为null，则返回false
	 * @param file 文件
	 * @return 如果存在返回true
	 */
	public static boolean exist(File file) {
		return (file == null) ? false : file.exists();
	}

	/**
	 * 是否存在匹配文件
	 * 
	 * @param directory 文件夹路径
	 * @param regexp 文件夹中所包含文件名的正则表达式
	 * @return 如果存在匹配文件返回true
	 */
	public static boolean exist(String directory, String regexp) {
		File file = new File(directory);
		if (!file.exists()) {
			return false;
		}

		String[] fileList = file.list();
		if (fileList == null) {
			return false;
		}

		for (String fileName : fileList) {
			if (fileName.matches(regexp)) {
				return true;
			}

		}
		return false;
	}

	/**
	 * 指定文件最后修改时间
	 * @param file 文件
	 * @return 最后修改时间
	 */
	public static Date lastModifiedTime(File file) {
		if (!exist(file)) {
			return null;
		}

		return new Date(file.lastModified());
	}

	/**
	 * 指定路径文件最后修改时间
	 * 
	 * @param path 路径
	 * @return 最后修改时间
	 */
	public static Date lastModifiedTime(String path) {
		File file = new File(path);
		if (!exist(file)) {
			return null;
		}

		return new Date(file.lastModified());
	}

	/**
	 * 创建文件及其父目录，如果这个文件存在，直接返回这个文件
	 * @param fullFilePath 文件的全路径，使用POSIX风格
	 * @return 文件，若路径为null，返回null
	 * @throws IOException
	 */
	public static File touch(String fullFilePath) throws IOException {
		if (fullFilePath == null) {
			return null;
		}
		return touch(file(fullFilePath));
	}

	/**
	 * 创建文件及其父目录，如果这个文件存在，直接返回这个文件
	 * @param file 文件对象
	 * @return 文件，若路径为null，返回null
	 * @throws IOException
	 */
	public static File touch(File file) throws IOException {
		if (null == file) {
			return null;
		}

		if (false == file.exists()) {
			mkParentDirs(file);
			file.createNewFile();
		}
		return file;
	}

	/**
	 * 创建文件及其父目录，如果这个文件存在，直接返回这个文件
	 * @param parent 父文件对象
	 * @param path 文件路径
	 * @return File
	 * @throws IOException 
	 */
	public static File touch(File parent, String path) throws IOException {
		return touch(file(parent, path));
	}

	/**
	 * 创建文件及其父目录，如果这个文件存在，直接返回这个文件
	 * @param parent 父文件对象
	 * @param path 文件路径
	 * @return File
	 * @throws IOException 
	 */
	public static File touch(String parent, String path) throws IOException {
		return touch(file(parent, path));
	}

	/**
	 * 创建所给文件或目录的父目录
	 * @param file 文件或目录
	 * @return 父目录
	 */
	public static File mkParentDirs(File file) {
		final File parentFile = file.getParentFile();
		if (null != parentFile && false == parentFile.exists()) {
			parentFile.mkdirs();
		}
		return parentFile;
	}

	/**
	 * 创建父文件夹，如果存在直接返回此文件夹
	 * @param path 文件夹路径，使用POSIX格式，无论哪个平台
	 * @return 创建的目录
	 */
	public static File mkParentDirs(String path) {
		if (path == null) {
			return null;
		}
		return mkParentDirs(file(path));
	}

	/**
	 * 删除文件或者文件夹
	 * @param fullFileOrDirPath 文件或者目录的路径
	 * @return 成功与否
	 * @throws IOException
	 */
	public static boolean del(String fullFileOrDirPath) throws IOException {
		return del(file(fullFileOrDirPath));
	}

	/**
	 * 删除文件或者文件夹
	 * 
	 * @param file 文件对象
	 * @return 成功与否
	 * @throws IOException
	 */
	public static boolean del(File file) throws IOException {
		if (file == null || file.exists() == false) {
			return true;
		}

		if (file.isDirectory()) {
			File[] files = file.listFiles();
			for (File childFile : files) {
				boolean isOk = del(childFile);
				if (isOk == false) {
					// 删除一个出错则本次删除任务失败
					return false;
				}
			}
		}
		return file.delete();
	}

	/**
	 * 创建文件夹，如果存在直接返回此文件夹
	 * 
	 * @param dirPath 文件夹路径，使用POSIX格式，无论哪个平台
	 * @return 创建的目录
	 */
	public static File mkdir(String dirPath) {
		if (dirPath == null) {
			return null;
		}
		File dir = file(dirPath);
		if (false == dir.exists()) {
			dir.mkdirs();
		}
		return dir;
	}

	/**
	 * 创建临时文件<br>
	 * 创建后的文件名为 prefix[Randon].tmp
	 * @param dir 临时文件创建的所在目录
	 * @return 临时文件
	 * @throws IOException
	 */
	public static File createTempFile(File dir) throws IOException {
		return createTempFile("hutool", null, dir, true);
	}

	/**
	 * 创建临时文件<br>
	 * 创建后的文件名为 prefix[Randon].tmp
	 * @param dir 临时文件创建的所在目录
	 * @param isReCreat 是否重新创建文件（删掉原来的，创建新的）
	 * @return 临时文件
	 * @throws IOException
	 */
	public static File createTempFile(File dir, boolean isReCreat) throws IOException {
		return createTempFile("hutool", null, dir, isReCreat);
	}

	/**
	 * 创建临时文件<br>
	 * 创建后的文件名为 prefix[Randon].suffix From com.jodd.io.FileUtils
	 * @param prefix 前缀，至少3个字符
	 * @param suffix 后缀，如果null则使用默认.tmp
	 * @param dir 临时文件创建的所在目录
	 * @param isReCreat 是否重新创建文件（删掉原来的，创建新的）
	 * @return 临时文件
	 * @throws IOException
	 */
	public static File createTempFile(String prefix, String suffix, File dir, boolean isReCreat) throws IOException {
		int exceptionsCount = 0;
		while (true) {
			try {
				File file = File.createTempFile(prefix, suffix, dir).getCanonicalFile();
				if (isReCreat) {
					file.delete();
					file.createNewFile();
				}
				return file;
			} catch (IOException ioex) { // fixes java.io.WinNTFileSystem.createFileExclusively access denied
				if (++exceptionsCount >= 50) {
					throw ioex;
				}
			}
		}
	}

	/**
	 * 复制文件或目录<br>
	 * 如果目标文件为目录，则将源文件以相同文件名拷贝到目标目录
	 * @param srcPath 源文件或目录
	 * @param destPath 目标文件或目录
	 * @param isOverride 是否覆盖目标文件
	 * @return 目标目录或文件
	 * @throws IOException
	 */
	public static File copy(String srcPath, String destPath, boolean isOverride) throws IOException {
		return copy(file(srcPath), file(destPath), isOverride);
	}

	/**
	 * 复制文件或目录<br>
	 * 情况如下：<br>
	 * 1、src和dest都为目录，则讲src下所有文件目录拷贝到dest下<br>
	 * 2、src和dest都为文件，直接复制，名字为dest<br>
	 * 3、src为文件，dest为目录，将src拷贝到dest目录下<br>
	 * 
	 * @param src 源文件
	 * @param dest 目标文件或目录
	 * @param isOverride 是否覆盖目标文件
	 * @return 目标目录或文件
	 * @throws IOException
	 */
	public static File copy(File src, File dest, boolean isOverride) throws IOException {
		// check
		if (!src.exists()) {
			throw new FileNotFoundException("File not exist: " + src);
		}
		if (equals(src, dest)) {
			throw new IOException("Files '" + src + "' and '" + dest + "' are equal");
		}

		// 复制目录
		if (src.isDirectory()) {
			if (dest.isFile()) {
				throw new IOException(StringUtils.format("Src [{}] is a directory but Dest [{}] is a file!", src.getPath(), dest.getPath()));
			}

			if (!dest.exists()) {
				dest.mkdirs();
			}
			String files[] = src.list();
			for (String file : files) {
				File srcFile = new File(src, file);
				File destFile = new File(dest, file);
				// 递归复制
				copy(srcFile, destFile, isOverride);
			}
			return dest;
		}

		// 检查目标
		if (dest.exists()) {
			if (dest.isDirectory()) {
				dest = new File(dest, src.getName());
			}
			if (false == isOverride) {
				// 不覆盖，直接跳过
				logger.debug(StringUtils.format("File [{}] already exist", dest));
				return dest;
			}
		} else {
			touch(dest);
		}

		// do copy file
		FileInputStream input = new FileInputStream(src);
		FileOutputStream output = new FileOutputStream(dest);
		try {
			IOUtils.copy(input, output);
		} finally {
			IOUtils.close(output);
			IOUtils.close(input);
		}

		if (src.length() != dest.length()) {
			throw new IOException("Copy file failed of '" + src + "' to '" + dest + "' due to different sizes");
		}

		return dest;
	}

	/**
	 * 移动文件或者目录
	 * 
	 * @param src 源文件或者目录
	 * @param dest 目标文件或者目录
	 * @param isOverride 是否覆盖目标
	 * @throws IOException
	 */
	public static void move(File src, File dest, boolean isOverride) throws IOException {
		// check
		if (!src.exists()) {
			throw new FileNotFoundException("File already exist: " + src);
		}
		if (dest.exists()) {
			if (isOverride) {
				dest.delete();
			} else {
				logger.debug(StringUtils.format("File [{}] already exist", dest));
			}
		}

		// 来源为文件夹，目标为文件
		if (src.isDirectory() && dest.isFile()) {
			throw new IOException(StringUtils.format("Can not move directory [{}] to file [{}]", src, dest));
		}

		// 来源为文件，目标为文件夹
		if (src.isFile() && dest.isDirectory()) {
			dest = new File(dest, src.getName());
		}

		if (src.renameTo(dest) == false) {
			// 在文件系统不同的情况下，renameTo会失败，此时使用copy，然后删除原文件
			try {
				copy(src, dest, isOverride);
				src.delete();
			} catch (Exception e) {
				throw new IOException(StringUtils.format("Move [{}] to [{}] failed!", src, dest), e);
			}

		}
	}



	/**
	 * 判断是否为目录，如果file为null，则返回false
	 * @param file 文件
	 * @return 如果为目录true
	 */
	public static boolean isDirectory(File file) {
		return (file == null) ? false : file.isDirectory();
	}

	/**
	 * <p>
	 * 根据文件路径判断该路径表示的是文件还是目录
	 * </p>
	 * @param filePath
	 * @return
	 */
	public static boolean isDirectory(String filePath) {
		if (!StringUtils.isEmpty(filePath)) {
			filePath = formatFilePath(filePath);
			int index1 = filePath.lastIndexOf('.');
			if (index1 == -1) {
				return true;
			} else {
				int index2 = filePath.lastIndexOf(UNIX_SEPARATOR) == -1 ? filePath.lastIndexOf(UNIX_SEPARATOR)
						: filePath.lastIndexOf(UNIX_SEPARATOR);
				if (index2 != -1) {
					if (index1 > index2) {
						return false;
					} else {
						return true;
					}
				} else {
					return false;
				}
			}
		}
		return false;
	}
	/**
	 * 判断是否为文件，如果path为null，则返回false
	 * 
	 * @param path 文件路径
	 * @return 如果为文件true
	 */
	public static boolean isFile(String path) {
		return (path == null) ? false : file(path).isFile();
	}

	/**
	 * 判断是否为文件，如果file为null，则返回false
	 * 
	 * @param file 文件
	 * @return 如果为文件true
	 */
	public static boolean isFile(File file) {
		return (file == null) ? false : file.isFile();
	}

	/**
	 * 检查两个文件是否是同一个文件
	 * 
	 * @param file1 文件1
	 * @param file2 文件2
	 * @return 是否相同
	 */
	public static boolean equals(File file1, File file2) {
		try {
			file1 = file1.getCanonicalFile();
			file2 = file2.getCanonicalFile();
		} catch (IOException ignore) {
			return false;
		}
		return file1.equals(file2);
	}

	/**
	 * 获得最后一个文件路径分隔符的位置
	 * 
	 * @param filePath 文件路径
	 * @return 最后一个文件路径分隔符的位置
	 */
	public static int indexOfLastSeparator(String filePath) {
		if (filePath == null) {
			return -1;
		}
		int lastUnixPos = filePath.lastIndexOf(UNIX_SEPARATOR);
		int lastWindowsPos = filePath.lastIndexOf(WINDOWS_SEPARATOR);
		return (lastUnixPos >= lastWindowsPos) ? lastUnixPos : lastWindowsPos;
	}

	/**
	 * 判断文件是否被改动<br>
	 * 如果文件对象为 null 或者文件不存在，被视为改动
	 * 
	 * @param file 文件对象
	 * @param lastModifyTime 上次的改动时间
	 * @return 是否被改动
	 */
	public static boolean isModifed(File file, long lastModifyTime) {
		if (null == file || false == file.exists()) {
			return true;
		}
		return file.lastModified() != lastModifyTime;
	}

	/**
	 * 修复路径<br>
	 * 1. 统一用 / <br>
	 * 2. 多个 / 转换为一个
	 * 
	 * @param path 原路径
	 * @return 修复后的路径
	 */
	public static String normalize(String path) {
		return path.replaceAll("[/\\\\]{1,}", "/");
	}

	/**
	 * 获得相对子路径
	 * 
	 * @param rootDir 绝对父路径
	 * @param filePath 文件路径
	 * @return 相对子路径
	 * @throws Exception 
	 */
	public static String subPath(String rootDir, String filePath) throws Exception {
		return subPath(rootDir, file(filePath));
	}

	/**
	 * 获得相对子路径
	 * 
	 * @param rootDir 绝对父路径
	 * @param file 文件
	 * @return 相对子路径
	 * @throws Exception 
	 */
	public static String subPath(String rootDir, File file) throws Exception {
		if (StringUtils.isEmpty(rootDir)) {
		}

		String subPath = null;
		try {
			subPath = file.getCanonicalPath();
		} catch (IOException e) {
			throw new Exception(e);
		}

		if (StringUtils.isNotEmpty(rootDir) && StringUtils.isNotEmpty(subPath)) {
			rootDir = normalize(rootDir);
			subPath = normalize(subPath);

			if (subPath != null && subPath.toLowerCase().startsWith(subPath.toLowerCase())) {
				subPath = subPath.substring(rootDir.length() + 1);
			}
		}
		return subPath;
	}

	// -------------------------------------------------------------------------------------------- name start
	/**
	 * 返回主文件名
	 * 
	 * @param file 文件
	 * @return 主文件名
	 */
	public static String mainName(File file) {
		if (file.isDirectory()) {
			return file.getName();
		}
		return mainName(file.getName());
	}

	/**
	 * 返回主文件名
	 * 
	 * @param fileName 完整文件名
	 * @return 主文件名
	 */
	public static String mainName(String fileName) {
		if (StringUtils.isBlank(fileName) || false == fileName.contains(StringUtils.DOT)) {
			return fileName;
		}
		return StringUtils.subPre(fileName, fileName.lastIndexOf(StringUtils.DOT));
	}

	/**
	 * 获取文件扩展名
	 * 
	 * @param file 文件
	 * @return 扩展名
	 */
	public static String extName(File file) {
		if (null == file) {
			return null;
		}
		if (file.isDirectory()) {
			return null;
		}
		return extName(file.getName());
	}

	/**
	 * 获得文件的扩展名
	 * 
	 * @param fileName 文件名
	 * @return 扩展名
	 */
	public static String extName(String fileName) {
		if (fileName == null) {
			return null;
		}
		int index = fileName.lastIndexOf(StringUtils.DOT);
		if (index == -1) {
			return StringUtils.EMPTY;
		} else {
			String ext = fileName.substring(index + 1);
			// 扩展名中不能包含路径相关的符号
			return (ext.contains(String.valueOf(UNIX_SEPARATOR)) || ext.contains(String.valueOf(WINDOWS_SEPARATOR))) ? StringUtils.EMPTY : ext;
		}
	}
	// -------------------------------------------------------------------------------------------- name end

	// -------------------------------------------------------------------------------------------- in start
	/**
	 * 获得输入流
	 * 
	 * @param file 文件
	 * @return 输入流
	 * @throws FileNotFoundException
	 */
	public static BufferedInputStream getInputStream(File file) throws FileNotFoundException {
		return new BufferedInputStream(new FileInputStream(file));
	}

	/**
	 * 获得输入流
	 * 
	 * @param path 文件路径
	 * @return 输入流
	 * @throws FileNotFoundException
	 */
	public static BufferedInputStream getInputStream(String path) throws FileNotFoundException {
		return getInputStream(file(path));
	}

	/**
	 * 获得一个文件读取器
	 * 
	 * @param file 文件
	 * @return BufferedReader对象
	 * @throws IOException
	 */
	public static BufferedReader getUtf8Reader(File file) throws IOException {
		return getReader(file, CharSetUtils.CHARSET_UTF_8);
	}

	/**
	 * 获得一个文件读取器
	 * 
	 * @param path 文件路径
	 * @return BufferedReader对象
	 * @throws IOException
	 */
	public static BufferedReader getUtf8Reader(String path) throws IOException {
		return getReader(path, CharSetUtils.CHARSET_UTF_8);
	}

	/**
	 * 获得一个文件读取器
	 * 
	 * @param file 文件
	 * @param charsetName 字符集
	 * @return BufferedReader对象
	 * @throws IOException
	 */
	public static BufferedReader getReader(File file, String charsetName) throws IOException {
		return IOUtils.getReader(getInputStream(file), charsetName);
	}

	/**
	 * 获得一个文件读取器
	 * 
	 * @param file 文件
	 * @param charset 字符集
	 * @return BufferedReader对象
	 * @throws IOException
	 */
	public static BufferedReader getReader(File file, Charset charset) throws IOException {
		return IOUtils.getReader(getInputStream(file), charset);
	}

	/**
	 * 获得一个文件读取器
	 * 
	 * @param path 绝对路径
	 * @param charset 字符集
	 * @return BufferedReader对象
	 * @throws IOException
	 */
	public static BufferedReader getReader(String path, Charset charset) throws IOException {
		return getReader(file(path), charset);
	}

	// -------------------------------------------------------------------------------------------- in end

	/**
	 * 读取文件所有数据<br>
	 * 文件的长度不能超过Integer.MAX_VALUE
	 * 
	 * @param file 文件
	 * @return 字节码
	 * @throws IOException
	 */
	public static byte[] readBytes(File file) throws IOException {
		// check
		if (!file.exists()) {
			throw new FileNotFoundException("File not exist: " + file);
		}
		if (!file.isFile()) {
			throw new IOException("Not a file:" + file);
		}

		long len = file.length();
		if (len >= Integer.MAX_VALUE) {
			throw new IOException("File is larger then max array size");
		}

		byte[] bytes = new byte[(int) len];
		FileInputStream in = null;
		try {
			in = new FileInputStream(file);
			in.read(bytes);
		} finally {
			IOUtils.close(in);
		}

		return bytes;
	}

	/**
	 * 读取文件内容
	 * 
	 * @param file 文件
	 * @return 内容
	 * @throws IOException
	 */
	public static String readUtf8String(File file) throws IOException {
		return readString(file, CharSetUtils.CHARSET_UTF_8);
	}

	/**
	 * 读取文件内容
	 * 
	 * @param path 文件路径
	 * @return 内容
	 * @throws IOException
	 */
	public static String readUtf8String(String path) throws IOException {
		return readString(path, CharSetUtils.CHARSET_UTF_8);
	}

	/**
	 * 读取文件内容
	 * 
	 * @param file 文件
	 * @param charsetName 字符集
	 * @return 内容
	 * @throws IOException
	 */
	public static String readString(File file, String charsetName) throws IOException {
		return new String(readBytes(file), charsetName);
	}

	/**
	 * 读取文件内容
	 * 
	 * @param file 文件
	 * @param charset 字符集
	 * @return 内容
	 * @throws IOException
	 */
	public static String readString(File file, Charset charset) throws IOException {
		return new String(readBytes(file), charset);
	}

	/**
	 * 读取文件内容
	 * 
	 * @param path 文件路径
	 * @param charsetName 字符集
	 * @return 内容
	 * @throws IOException
	 */
	public static String readString(String path, String charsetName) throws IOException {
		return readString(file(path), charsetName);
	}

	/**
	 * 读取文件内容
	 * 
	 * @param path 文件路径
	 * @param charset 字符集
	 * @return 内容
	 * @throws IOException
	 */
	public static String readString(String path, Charset charset) throws IOException {
		return readString(file(path), charset);
	}

	/**
	 * 读取文件内容
	 * 
	 * @param url 文件URL
	 * @param charset 字符集
	 * @return 内容
	 * @throws IOException
	 */
	public static String readString(URL url, String charset) throws IOException {
		if (url == null) {
			throw new RuntimeException("Empty url provided!");
		}

		InputStream in = null;
		try {
			in = url.openStream();
			return IOUtils.read(in, charset);
		} finally {
			IOUtils.close(in);
		}
	}

	/**
	 * 从文件中读取每一行数据
	 * 
	 * @param path 文件路径
	 * @param charset 字符集
	 * @param collection 集合
	 * @return 文件中的每行内容的集合
	 * @throws IOException
	 */
	public static <T extends Collection<String>> T readLines(String path, String charset, T collection) throws IOException {
		return readLines(file(path), charset, collection);
	}

	/**
	 * 从文件中读取每一行数据
	 * 
	 * @param file 文件路径
	 * @param charset 字符集
	 * @param collection 集合
	 * @return 文件中的每行内容的集合
	 * @throws IOException
	 */
	public static <T extends Collection<String>> T readLines(File file, String charset, T collection) throws IOException {
		BufferedReader reader = null;
		try {
			reader = getReader(file, charset);
			String line;
			while (true) {
				line = reader.readLine();
				if (line == null) break;
				collection.add(line);
			}
			return collection;
		} finally {
			IOUtils.close(reader);
		}
	}

	/**
	 * 从文件中读取每一行数据
	 * 
	 * @param url 文件的URL
	 * @param charset 字符集
	 * @param collection 集合
	 * @return 文件中的每行内容的集合
	 * @throws IOException
	 */
	public static <T extends Collection<String>> T readLines(URL url, String charset, T collection) throws IOException {
		InputStream in = null;
		try {
			in = url.openStream();
			return IOUtils.readLines(in, charset, collection);
		} finally {
			IOUtils.close(in);
		}
	}

	/**
	 * 从文件中读取每一行数据
	 * @param url 文件的URL
	 * @param charset 字符集
	 * @return 文件中的每行内容的集合List
	 * @throws IOException
	 */
	public static List<String> readLines(URL url, String charset) throws IOException {
		return readLines(url, charset, new ArrayList<String>());
	}

	/**
	 * 从文件中读取每一行数据
	 * @param path 文件路径
	 * @param charset 字符集
	 * @return 文件中的每行内容的集合List
	 * @throws IOException
	 */
	public static List<String> readLines(String path, String charset) throws IOException {
		return readLines(path, charset, new ArrayList<String>());
	}

	/**
	 * 从文件中读取每一行数据
	 * 
	 * @param file 文件
	 * @param charset 字符集
	 * @return 文件中的每行内容的集合List
	 * @throws IOException
	 */
	public static List<String> readLines(File file, String charset) throws IOException {
		return readLines(file, charset, new ArrayList<String>());
	}

	/**
	 * 读取JSON
	 * 
	 * @param file JSON文件
	 * @param charsetName 编码
	 * @return JSON（包括JSONObject和JSONArray）
	 * @throws IOException
	 */
	public static JSON readJSON(File file, String charsetName) throws IOException {
		return JSON.parseObject(readString(file, charsetName));
	}

	/**
	 * 读取JSONObject
	 * 
	 * @param file JSON文件
	 * @param charset 编码
	 * @return JSONObject
	 * @throws IOException
	 */
	public static JSONObject readJSONObject(File file, Charset charset) throws IOException {
		return JSON.parseObject(readString(file, charset));
	}

	/**
	 * 读取JSONObject
	 * @param file JSON文件
	 * @param charsetName 编码
	 * @return JSONObject
	 * @throws IOException
	 */
	public static JSONObject readJSONObject(File file, String charsetName) throws IOException {
		return JSON.parseObject(readString(file, charsetName));
	}

	/**
	 * 读取JSONArray
	 * 
	 * @param file JSON文件
	 * @param charset 编码
	 * @return JSONArray
	 * @throws IOException
	 */
	public static JSONArray readJSONArray(File file, Charset charset) throws IOException {
		return JSON.parseArray(readString(file, charset));
	}

	/**
	 * 读取JSONArray
	 * 
	 * @param file JSON文件
	 * @param charsetName 编码
	 * @return JSONArray
	 * @throws IOException
	 */
	public static JSONArray readJSONArray(File file, String charsetName) throws IOException {
		return JSON.parseArray(readString(file, charsetName));
	}

	/**
	 * 按照给定的readerHandler读取文件中的数据
	 * 
	 * @param readerHandler Reader处理类
	 * @param path 文件的绝对路径
	 * @param charset 字符集
	 * @return 从文件中load出的数据
	 * @throws IOException
	 */
	public static <T> T load(ReaderHandler<T> readerHandler, String path, String charset) throws IOException {
		BufferedReader reader = null;
		T result = null;
		try {
			reader = getReader(path, charset);
			result = readerHandler.handle(reader);
		} catch (IOException e) {
			throw new IOException(e);
		} finally {
			IOUtils.close(reader);
		}
		return result;
	}

	// -------------------------------------------------------------------------------------------- out start
	/**
	 * 获得一个输出流对象
	 * 
	 * @param file 文件
	 * @return 输出流对象
	 * @throws IOException
	 */
	public static BufferedOutputStream getOutputStream(File file) throws IOException {
		return new BufferedOutputStream(new FileOutputStream(touch(file)));
	}

	/**
	 * 获得一个输出流对象
	 * @param path 输出到的文件路径，绝对路径
	 * @return 输出流对象
	 * @throws IOException
	 */
	public static BufferedOutputStream getBufferedOutputStream(String path) throws IOException {
		return getOutputStream(touch(path));
	}

	/**
	 * 获得一个带缓存的写入对象
	 * 
	 * @param path 输出路径，绝对路径
	 * @param charsetName 字符集
	 * @param isAppend 是否追加
	 * @return BufferedReader对象
	 * @throws IOException
	 */
	public static BufferedWriter getWriter(String path, String charsetName, boolean isAppend) throws IOException {
		return getWriter(touch(path), Charset.forName(charsetName), isAppend);
	}

	/**
	 * 获得一个带缓存的写入对象
	 * 
	 * @param path 输出路径，绝对路径
	 * @param charset 字符集
	 * @param isAppend 是否追加
	 * @return BufferedReader对象
	 * @throws IOException
	 */
	public static BufferedWriter getWriter(String path, Charset charset, boolean isAppend) throws IOException {
		return getWriter(touch(path), charset, isAppend);
	}

	/**
	 * 获得一个带缓存的写入对象
	 * 
	 * @param file 输出文件
	 * @param charsetName 字符集
	 * @param isAppend 是否追加
	 * @return BufferedReader对象
	 * @throws IOException
	 */
	public static BufferedWriter getWriter(File file, String charsetName, boolean isAppend) throws IOException {
		return getWriter(file, Charset.forName(charsetName), isAppend);
	}

	/**
	 * 获得一个带缓存的写入对象
	 * 
	 * @param file 输出文件
	 * @param charset 字符集
	 * @param isAppend 是否追加
	 * @return BufferedReader对象
	 * @throws IOException
	 */
	public static BufferedWriter getWriter(File file, Charset charset, boolean isAppend) throws IOException {
		if (false == file.exists()) {
			file.createNewFile();
		}
		return new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, isAppend), charset));
	}

	/**
	 * 获得一个打印写入对象，可以有print
	 * 
	 * @param path 输出路径，绝对路径
	 * @param charset 字符集
	 * @param isAppend 是否追加
	 * @return 打印对象
	 * @throws IOException
	 */
	public static PrintWriter getPrintWriter(String path, String charset, boolean isAppend) throws IOException {
		return new PrintWriter(getWriter(path, charset, isAppend));
	}

	/**
	 * 获得一个打印写入对象，可以有print
	 * 
	 * @param file 文件
	 * @param charset 字符集
	 * @param isAppend 是否追加
	 * @return 打印对象
	 * @throws IOException
	 */
	public static PrintWriter getPrintWriter(File file, String charset, boolean isAppend) throws IOException {
		return new PrintWriter(getWriter(file, charset, isAppend));
	}

	// -------------------------------------------------------------------------------------------- out end

	/**
	 * 将String写入文件，覆盖模式，字符集为UTF-8
	 * 
	 * @param content 写入的内容
	 * @param path 文件路径
	 * @return 写入的文件
	 * @throws IOException
	 */
	public static File writeUtf8String(String content, String path) throws IOException {
		return writeString(content, path, CharSetUtils.UTF_8);
	}

	/**
	 * 将String写入文件，覆盖模式，字符集为UTF-8
	 * 
	 * @param content 写入的内容
	 * @param file 文件
	 * @return 写入的文件
	 * @throws IOException
	 */
	public static File writeUtf8String(String content, File file) throws IOException {
		return writeString(content, file, CharSetUtils.UTF_8);
	}

	/**
	 * 将String写入文件，覆盖模式
	 * 
	 * @param content 写入的内容
	 * @param path 文件路径
	 * @param charset 字符集
	 * @return 写入的文件
	 * @throws IOException
	 */
	public static File writeString(String content, String path, String charset) throws IOException {
		return writeString(content, touch(path), charset);
	}

	/**
	 * 将String写入文件，覆盖模式
	 * 
	 * @param content 写入的内容
	 * @param file 文件
	 * @param charset 字符集
	 * @throws IOException
	 */
	public static File writeString(String content, File file, String charset) throws IOException {
		PrintWriter writer = null;
		try {
			writer = getPrintWriter(file, charset, false);
			writer.print(content);
			writer.flush();
		} finally {
			IOUtils.close(writer);
		}
		return file;
	}

	/**
	 * 将String写入文件，追加模式
	 * 
	 * @param content 写入的内容
	 * @param path 文件路径
	 * @param charset 字符集
	 * @return 写入的文件
	 * @throws IOException
	 */
	public static File appendString(String content, String path, String charset) throws IOException {
		return appendString(content, touch(path), charset);
	}

	/**
	 * 将String写入文件，追加模式
	 * 
	 * @param content 写入的内容
	 * @param file 文件
	 * @param charset 字符集
	 * @return 写入的文件
	 * @throws IOException
	 */
	public static File appendString(String content, File file, String charset) throws IOException {
		PrintWriter writer = null;
		try {
			writer = getPrintWriter(file, charset, true);
			writer.print(content);
			writer.flush();
		} finally {
			IOUtils.close(writer);
		}
		return file;
	}

	/**
	 * 将列表写入文件，覆盖模式
	 * 
	 * @param list 列表
	 * @param path 绝对路径
	 * @param charset 字符集
	 * @throws IOException
	 */
	public static <T> void writeLines(Collection<T> list, String path, String charset) throws IOException {
		writeLines(list, path, charset, false);
	}

	/**
	 * 将列表写入文件，追加模式
	 * 
	 * @param list 列表
	 * @param path 绝对路径
	 * @param charset 字符集
	 * @throws IOException
	 */
	public static <T> void appendLines(Collection<T> list, String path, String charset) throws IOException {
		writeLines(list, path, charset, true);
	}

	/**
	 * 将列表写入文件
	 * 
	 * @param list 列表
	 * @param path 绝对路径
	 * @param charset 字符集
	 * @param isAppend 是否追加
	 * @throws IOException
	 */
	public static <T> void writeLines(Collection<T> list, String path, String charset, boolean isAppend) throws IOException {
		PrintWriter writer = null;
		try {
			writer = getPrintWriter(path, charset, isAppend);
			for (T t : list) {
				if (t != null) {
					writer.println(t.toString());
					writer.flush();
				}
			}
		} finally {
			IOUtils.close(writer);
		}
	}

	/**
	 * 写数据到文件中
	 * 
	 * @param data 数据
	 * @param path 目标文件
	 * @return File
	 * @throws IOException
	 */
	public static File writeBytes(byte[] data, String path) throws IOException {
		return writeBytes(data, touch(path));
	}

	/**
	 * 写数据到文件中
	 * 
	 * @param dest 目标文件
	 * @param data 数据
	 * @return dest
	 * @throws IOException
	 */
	public static File writeBytes(byte[] data, File dest) throws IOException {
		return writeBytes(data, dest, 0, data.length, false);
	}

	/**
	 * 写入数据到文件
	 * 
	 * @param data 数据
	 * @param dest 目标文件
	 * @param off
	 * @param len
	 * @param append
	 * @return dest
	 * @throws IOException
	 */
	public static File writeBytes(byte[] data, File dest, int off, int len, boolean append) throws IOException {
		if (dest.exists() == true) {
			if (dest.isFile() == false) {
				throw new IOException("Not a file: " + dest);
			}
		}
		FileOutputStream out = null;
		try {
			out = new FileOutputStream(dest, append);
			out.write(data, off, len);
			out.flush();
		} finally {
			IOUtils.close(out);
		}
		return dest;
	}

	/**
	 * 将流的内容写入文件<br>
	 * 
	 * @param dest 目标文件
	 * @param in 输入流
	 * @return dest
	 * @throws IOException
	 */
	public static File writeFromStream(InputStream in, File dest) throws IOException {
		FileOutputStream out = null;
		try {
			out = new FileOutputStream(dest);
			IOUtils.copy(in, out);
		} finally {
			IOUtils.close(out);
		}
		return dest;
	}

	/**
	 * 将流的内容写入文件<br>
	 * 
	 * @param in 输入流
	 * @param fullFilePath 文件绝对路径
	 * @return dest
	 * @throws IOException
	 */
	public static File writeFromStream(InputStream in, String fullFilePath) throws IOException {
		return writeFromStream(in, touch(fullFilePath));
	}

	/**
	 * 将文件写入流中
	 * 
	 * @param file 文件
	 * @param out 流
	 * @throws IOException
	 */
	public static void writeToStream(File file, OutputStream out) throws IOException {
		FileInputStream in = null;
		try {
			in = new FileInputStream(file);
			IOUtils.copy(in, out);
		} finally {
			IOUtils.close(in);
		}
	}

	/**
	 * 将流的内容写入文件<br>
	 * 
	 * @param fullFilePath 文件绝对路径
	 * @param out 输出流
	 * @throws IOException
	 */
	public static void writeToStream(String fullFilePath, OutputStream out) throws IOException {
		writeToStream(touch(fullFilePath), out);
	}

	/**
	 * 可读的文件大小
	 * 
	 * @param file 文件
	 * @return 大小
	 */
	public static String readableFileSize(File file) {
		return readableFileSize(file.length());
	}

	/**
	 * 可读的文件大小<br>
	 * 参考 http://stackoverflow.com/questions/3263892/format-file-size-as-mb-gb-etc
	 * 
	 * @param size Long类型大小
	 * @return 大小
	 */
	public static String readableFileSize(long size) {
		if (size <= 0) return "0";
		final String[] units = new String[] { "B", "kB", "MB", "GB", "TB", "EB" };
		int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
		return new DecimalFormat("#,##0.##").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups];
	}

}
@SuppressWarnings("rawtypes")
class FileWrapper implements Comparable {
	/** File */
	private File file;

	public FileWrapper( File file ) {
		this.file = file;
	}

	@Override
	public int compareTo( Object obj ) {
		assert obj instanceof FileWrapper;

		FileWrapper castObj = ( FileWrapper ) obj;

		if ( this.file.getName().compareTo( castObj.getFile().getName() ) > 0 ) {
			return 1;
		} else if ( this.file.getName().compareTo( castObj.getFile().getName() ) < 0 ) {
			return -1;
		} else {
			return 0;
		}
	}

	public File getFile() {
		return this.file;
	}

}
